import sys
import threading
import socket
from txosc import osc

from Bases import BaseObject
from osc_sender import UnicastOSCSender, MulticastOSCSender
from osc_receiver import UnicastOSCReceiver, MulticastOSCReceiver

#from ..BaseIO import BaseIO
from .. import BaseIO
from config import Config
from osc_base import OSCNode

def join_address(*args):
    s = '/'.join(['/'.join(arg.split('/')) for arg in args])
    return s
    
def issequence(obj):
    for t in [list, tuple, set]:
        if isinstance(obj, t):
            return True
    return False

class Client(BaseObject):
    def __init__(self, **kwargs):
        self._isMaster = None
        self._sendAllUpdates = False
        super(Client, self).__init__(**kwargs)
        self.app_address = kwargs.get('app_address')
        self.name = kwargs.get('name')
        self.address = kwargs.get('address')
        self.port = kwargs.get('port')
        self.hostaddr = (self.address, self.port)
        self.isLocalhost = kwargs.get('isLocalhost', socket.gethostname() in self.name)
        self.isMaster = kwargs.get('isMaster', False)
        self.discovered = kwargs.get('discovered', False)
        self.master_priority = kwargs.get('master_priority')
        #self.isSlave = kwargs.get('isSlave', self.app_address in self.name and not self.isLocalhost)
        print 'client: name=%s, local=%s, master=%s, slave=%s, sendall=%s' % (self.name, self.isLocalhost, self.isMaster, self.isSlave, self.sendAllUpdates)
    @property
    def isMaster(self):
        return self._isMaster
    @isMaster.setter
    def isMaster(self, value):
        if value != self._isMaster:
            self._isMaster = value
    @property
    def isSlave(self):
        return self.app_address in self.name and not self.isLocalhost and not self.isMaster
    @property
    def sendAllUpdates(self):
        if self._sendAllUpdates:
            return True
        return self.isMaster or self.isSlave and not self.isLocalhost
    @sendAllUpdates.setter
    def sendAllUpdates(self, value):
        self._sendAllUpdates = value

class oscIO(BaseIO.BaseIO, Config):
    io_classes = {'Unicast':(UnicastOSCReceiver, UnicastOSCSender), 
                  'Multicast':(MulticastOSCReceiver, MulticastOSCSender)}
    _confsection = 'OSC'
    def __init__(self, **kwargs):
        self.comm = kwargs.get('comm')
        BaseIO.BaseIO.__init__(self, **kwargs)
        Config.__init__(self, **kwargs)
        self.register_signal('client_added', 'client_removed', 'unique_address_changed', 'new_master')
        self.app_address = kwargs.get('app_address', self.get_conf('app_address', 'OSCApp'))
        self.default_root_address = kwargs.get('root_address', '%s-%s' % (self.app_address, socket.gethostname()))
        self.root_address = self.default_root_address
        self._master_priority = int(self.get_conf('master_priority', 10))
        self.wildcard_address = None
        self.osc_tree = OSCNode(root_node=True, transmit_callback=self.on_node_tree_send, 
                                get_client_cb=self.get_client)
        self.hostdata = {}
        self.hostdata_info = {'mcastaddr':{'conf':'multicast_address', 'default':'224.168.2.9'}, 
                              'mcastport':{'conf':'multicast_port', 'default':18889}, 
                              'hostaddr':{'conf':'host_address', 'default':BaseIO.detect_usable_address()}, 
                              'sendport':{'conf':'send_port', 'default':18889}, 
                              'recvport':{'conf':'recv_port', 'default':18888}}
        for k, v in self.hostdata_info.iteritems():
            val = kwargs.get(k, self.get_conf(v['conf'], v['default']))
            d = {k:type(v['default'])(val)}
            update = k in kwargs
            d.update({'update_conf':update})
            v.update({'update_conf':update})
            self.set_hostdata(**d)
        #print 'hostdata:',  self.hostdata
        self._iotype = None
        self._sender = None
        self._receiver = None
        self.oscMaster = None
        self.set_master_timeout = None
        self.master_takeover_timer = None
        self.check_master_attempts = None
        self.init_clients()
        s = kwargs.get('use_unique_addresses', self.get_conf('use_unique_addresses', 'True'))
        flag = not s == 'False'
        self.set_use_unique_address(flag, update_conf=False)
        io = kwargs.get('connection_type', self.get_conf('connection_type', 'Multicast'))
        self.build_io(iotype=io, update_conf=False)
        
        self.root_node.addCallback('/getMaster', self.on_master_requested_by_osc)
        self.root_node.addCallback('/setMaster', self.on_master_set_by_osc)
        
        serv = self.comm.ServiceConnector.add_service(name=self.root_address, 
                                                      stype='_osc._udp', 
                                                      port=self.hostdata['recvport'], 
                                                      text={'master_priority':self.master_priority})
        self.comm.ServiceConnector.connect('new_host', self.on_host_discovered)
        self.comm.ServiceConnector.connect('remove_host', self.on_host_removed)
        self.comm.ServiceConnector.add_listener(stype='_osc._udp')
        
    @property
    def master_priority(self):
        return self._master_priority
    @master_priority.setter
    def master_priority(self, value):
        if value != self.master_priority:
            self._master_priority = value
            self.update_conf(master_priority=value)
        
    @property
    def iotype(self):
        return self._iotype
    @iotype.setter
    def iotype(self, value):
        if value != self._iotype:
            self._iotype = value
            self.set_address_vars()
            
    @property
    def isMaster(self):
        return self.oscMaster == self.root_address
            
    def set_use_unique_address(self, flag, update_conf=True):
        self.use_unique_address = flag
        if update_conf:
            self.update_conf(use_unique_addresses=flag)
        self.set_address_vars()
        self.emit('unique_address_changed', state=flag)
    
    def set_address_vars(self):
        if self.iotype == 'Multicast' or self.use_unique_address:
            self.root_address = self.default_root_address
            self.add_client_name(None, update_conf=False)
        else:
            self.root_address = self.app_address
            self.root_node.setName(self.root_address)
    
    def set_hostdata(self, **kwargs):
        for key, val in kwargs.iteritems():
            if key in self.hostdata_info:
                setattr(self, key, val)
                self.hostdata.update({key:val})
        update = kwargs.get('update_conf')
        if update is not None:
            del kwargs['update_conf']
            update = False
        if update:
            #self.update_conf(**kwargs)
            self.update_hostdata()
            
    def update_hostdata(self):
        for key in self.hostdata_info.iterkeys():
            self.hostdata.update({key:getattr(self, key)})
        d = {}
        for key, val in self.hostdata.iteritems():
            update = self.hostdata_info[key].get('update_conf', True)
            if update:
                d.update({self.hostdata_info[key]['conf']:val})
        self.update_conf(**d)
    
    def init_clients(self):
        s = 'null'
        self.root_node = self.osc_tree.add_new_node(name=s)
        names = self.get_conf('client_names', [s])
        if type(names) == str:
            names = [names]
        self.clients = {}
        self.clients_by_address = {}
        self.client_names = set(names)
        self.add_client_name(None, update_conf=False)
        addresses = self.get_conf('client_addresses', [])
        if type(addresses) == str:
            addresses = [addresses]
        self.client_addresses = set(addresses)
        self.add_client_address(None, update_conf=False)
    
    def build_io(self, **kwargs):
        iotype = kwargs.get('iotype')
        if iotype is None:
            iotype = self.iotype
        update_conf = kwargs.get('update_conf', True)
        self.iotype = iotype
        iokwargs = {'root_address':self.root_address, 'osc_tree':self.osc_tree}
        iokwargs.update(self.hostdata)
        
        if self.connected:
            self.do_disconnect()
        
        self._receiver = self.io_classes[iotype][0](**iokwargs)
        self._sender = self.io_classes[iotype][1](**iokwargs)
        if update_conf:
            self.update_conf(connection_type=iotype)
        
    def add_client(self, **kwargs):
        kwargs.setdefault('port', self.hostdata['sendport'])
        kwargs.setdefault('app_address', self.app_address)
        client = Client(**kwargs)
        self.clients.update({client.name:client})
        self.clients_by_address.update({client.address:client})
        self.update_wildcards()
        if self.check_master_attempts is None:
            self.check_for_master(client=client.name)
        print 'add_client:', kwargs
        keys = ['name', 'address', 'port']
        s_kwargs = dict(zip(keys, [getattr(client, key) for key in keys]))
        s_kwargs.update({'client':client})
        self.emit('client_added', **s_kwargs)
            
    def old_add_client(self, **kwargs):
        name = kwargs.get('name')
        address = kwargs.get('address')
        port = int(kwargs.get('port', self.hostdata['sendport']))
        send_all = kwargs.get('send_all_updates', True)
        #port = self.hostdata['sendport']
        hostaddr = (address, port)
        self.clients.update({name:hostaddr})
        self.clients_by_address.update({hostaddr[0]:name})
        if True:#self.app_address in name:
            self.add_client_name(name, update_conf=False)
            if send_all:
                self.add_client_address((address, port), update_conf=False)
        print 'add_client:', name, self.clients[name]
        
        #print 'root=%s, wcnodes=%s, client_names=%s, root_address=%s' % (self.root_node._name, self.osc_tree._wildcardNodes, self.client_names, self.root_address)
        self.emit('client_added', name=name, address=address, port=port)
        
    def remove_client(self, **kwargs):
        name = kwargs.get('name')
        client = self.clients.get(name)
        if client:
            #self.remove_client_name(name, update_conf=False)
            #self.remove_client_address(addr, update_conf=False)
            del self.clients[name]
            self.update_wildcards()
            print 'remove_client:', name
            self.emit('client_removed', name=name, client=client)
            
    
    def update_wildcards(self):
        if self.wildcard_address:
            self.osc_tree._wildcardNodes.discard(self.wildcard_address)
            self.osc_tree._wildcardNodes.discard('{null}')
        names = []
        for c in self.clients.itervalues():
            if not c.isLocalhost:
                names.append(c.name)
        if names:
            s = '{%s}' % (','.join(names))
            self.wildcard_address = s
            self.root_node.setName(s)
            self.osc_tree._wildcardNodes.add(s)
            print 'wildcard = ', s
            print 'root_node = ', s
            print 'wcnodes = ', self.osc_tree._wildcardNodes
        
    def add_client_name(self, name, update_conf=True):
        s = self.build_curly_wildcard(self.client_names)
        self.osc_tree._wildcardNodes.discard(s)
        if name is not None:
            self.client_names.add(name)
        if len(self.client_names) > 1 and 'null' in self.client_names:
            self.client_names.discard('null')
        s = self.build_curly_wildcard(self.client_names)
        self.root_node.setName(s)
        self.osc_tree._wildcardNodes.add(s)
        if update_conf:
            self.update_conf(client_names=list(self.client_names))
        #self.emit('client_added', name=name)
        
    def remove_client_name(self, name, update_conf=True):
        s = self.build_curly_wildcard(self.client_names)
        self.osc_tree._wildcardNodes.discard(s)
        self.client_names.discard(name)
        s = self.build_curly_wildcard(self.client_names)
        self.root_node.setName(s)
        self.osc_tree._wildcardNodes.add(s)
        if update_conf:
            self.update_conf(client_names=list(self.client_names))
    
    def add_client_address(self, address, update_conf=True):
        if address is not None:
            self.client_addresses.add(address)
        if update_conf:
            self.update_conf(client_addresses=list(self.client_addresses))
        #self.emit('client_added', name=address)
        
    def remove_client_address(self, address, update_conf=True):
        self.client_addresses.discard(address)
        if update_conf:
            self.update_conf(client_addresses=list(self.client_addresses))
        
    def build_curly_wildcard(self, l):
        return '{%s}' % (','.join(list(l)))
        
    def do_connect(self, **kwargs):
        self.update_hostdata()
        new_kwargs = kwargs.copy()
        new_kwargs.update(self.hostdata)
        if self._receiver is None and self._sender is None:
            self.build_io()
        try:
            for obj in [self._receiver, self._sender]:
                obj.do_connect(**kwargs)
            print 'osc connected'
            self.connected = True
            self.check_for_master()
        except socket.error, msg:
            print 'osc socket error: ', msg
        
    def do_disconnect(self, **kwargs):
        for obj in [self._receiver, self._sender]:
            obj.do_disconnect(**kwargs)
        self.connected = False
        self.set_master(False)
        
    def get_client(self, **kwargs):
        hostaddr = kwargs.get('hostaddr')[0]
        #print hostaddr, self.clients_by_address
        return self.clients_by_address.get(hostaddr)
    
    def on_node_tree_send(self, **kwargs):
        client_name = kwargs.get('client')
        to_master = kwargs.get('to_master', client_name is None and not self.isMaster)
        if to_master:
            client_name = self.oscMaster
        client = None
        if client_name:
            client = self.clients.get(client_name)
        address = kwargs.get('address')
        root_address = kwargs.get('root_address', self.root_address)
        address[0] = root_address
        path = '/' + join_address(*address)
        if path[-1:] == '/':
            path = path[:-1]
        args = self.pack_args(kwargs.get('value'))
        msg = osc.Message(path, *args)
        if self._sender is not None:
            self._sender.preprocess(msg)
            if self.iotype == 'Multicast':
                #print 'osc_send: ', msg
                self._sender._send(msg)
            elif self.iotype == 'Unicast':
                clients = set()
                if client:
                    clients.add(client)
                else:
                    for c in self.clients.itervalues():
                        if c.sendAllUpdates:
                            clients.add(c)
                for c in clients:
                    self._sender._send(msg, c.hostaddr)
        
    def pack_args(self, value):
        if isinstance(value, list) or isinstance(value, tuple):
            return value
        elif value is None:
            return []
        return [value]
        
    def check_for_master(self, **kwargs):
        if self.connected and not self.isMaster:
            name = kwargs.get('name')
            if self.check_master_attempts is None:
                self.check_master_attempts = 0
            self.cancel_check_master_timer()
            self.set_master_timeout = threading.Timer(3.0, self.on_check_master_timeout)
            self.set_master_timeout.start()
            new_kwargs = {'address':'getMaster'}
            if name:
                new_kwargs.update({'client':name})
            self.root_node.send_message(**new_kwargs)
            
    def on_check_master_timeout(self):
        self.check_master_attempts += 1
        if self.check_master_attempts == 3:
            self.check_master_attempts = None
            self.set_master()
        else:
            self.check_for_master()
        
    def set_master(self, name=None):
        if self.master_takeover_timer:
            self.master_takeover_timer.cancel()
        if name is None:
            s = self.root_address
        elif name is False:
            s = None
        else:
            s = name
        if s != self.oscMaster:
            if self.oscMaster:
                c = self.clients.get(self.oscMaster)
                if c:
                    c.isMaster = False
                c = self.clients.get(s)
                if c:
                    c.isMaster = True
            self.oscMaster = s
            if self.isMaster:
                self.root_node.send_message(address='setMaster', value=self.oscMaster)
            elif self.determine_next_master().isLocalhost:
                t = threading.Timer(10.0, self.on_master_takeover_timeout)
                self.master_takeover_timer = t
                t.start()
        
        print 'master = ', self.oscMaster
        self.root_node.oscMaster = self.isMaster
        self.emit('new_master', master=self.oscMaster, master_is_local=self.isMaster)
        
    def on_master_takeover_timeout(self):
        m = self.determine_next_master()
        if not self.isMaster and m.isLocalhost:
            self.set_master()
            
    def cancel_check_master_timer(self):
        if self.set_master_timeout and self.set_master_timeout.isAlive:
            self.set_master_timeout.cancel()
            
    def determine_next_master(self):
        d = {}
        for client in self.clients.itervalues():
            key = client.master_priority
            if key is not None and (client.isSlave or client.isMaster or client.isLocalhost):
                if key in d:
                    if d[key].name < client.name:
                        d[key] = client
                else:
                    d[key] = client
        print 'clients by priority: ', d
        return d[min(d)]
        
    def select_new_master(self):
        self.oscMaster = None
        m = self.determine_next_master()
        if m.name == self.root_address:
            self.set_master()
        else:
            self.check_for_master()
                
#        names = self.clients.keys()
#        #names.append(self.root_address)
#        names.sort()
#        if names[0] == self.root_address:
#            self.set_master()
#        else:
#            self.check_for_master()        
        
    def on_master_requested_by_osc(self, msg, address):
        if self.oscMaster:
            self.root_node.send_message(address='setMaster', value=self.oscMaster)
        print 'master_requested_by_osc'
            
    def on_master_set_by_osc(self, msg, address):
        name = msg.getValues()[0]
        print 'master_set_by_osc', name
        self.cancel_check_master_timer()
        self.check_master_attempts = None
        self.set_master(name)
        
    def on_host_discovered(self, **kwargs):
        host = kwargs.get('host')
        #print 'discover:', host.hostdata
        if '.' in host.address:
            c_kwargs = host.hostdata.copy()
            txt = host.hostdata.get('text', {})
            m = txt.get('master_priority')
            if m:
                c_kwargs.update({'master_priority':int(m)})
            c_kwargs.update({'discovered':True})
            self.add_client(**c_kwargs)
        #print 'discover:', host.hostdata
        #if socket.gethostname() not in host.hostname:
        #    if host.name not in self.client_names and '.' in host.address:
        #        self.add_client(**host.hostdata)
            
        
    def on_host_removed(self, **kwargs):
        print 'remove:', kwargs
        id = kwargs.get('id')
        self.remove_client(name=id)
        if id == self.oscMaster:
            self.select_new_master()


